package com.example.base.tools.upgrade

import android.content.Context
import android.content.Intent
import android.net.Uri
import android.os.Build
import android.os.Environment
import android.widget.Toast
import androidx.core.content.FileProvider
import androidx.fragment.app.FragmentActivity
import androidx.fragment.app.commit
import com.example.base.ext.toast
import java.io.File
import java.io.FileInputStream
import java.io.FileNotFoundException
import java.io.IOException
import java.math.BigInteger
import java.security.MessageDigest
import java.security.NoSuchAlgorithmException


/**  APP 升级工具类  **/
object UpgradeTool : DownloadListener {

    private var isInit = false
    private lateinit var activity: FragmentActivity

    /**  fragment 的 tag  **/
    private const val FRAGMENT_TAG = "DownloadFragment"
    private val downloadInfo: DownloadInfo by lazy {
        DownloadInfo()
    }

    /**  7.0 自定义的 fileProvider 名称  **/
    private var providerAuthority: String = ""
    private lateinit var fragment: DownloadFragment
    private val listener: UpgradeListener by lazy {
        UpgradeListener()
    }
    private lateinit var innerFun: UpgradeListener.() -> Unit

    /**  默认地址  **/
    fun defaultPath(context: Context) =
        context.getExternalFilesDir(Environment.DIRECTORY_DOWNLOADS)?.path ?: ""

    /**
     * 初始化升级工具, 设置更新 apk 的自定义储存路径
     * @param fileSavePath APK 文件下载完毕后的存储路径, 不填就是默认路径, 默认在 Android/包名/files/Download/ 下
     * @param providerAuthority 7.0+ 自定义 fileProvider 的名称
     */
    @Synchronized
    fun init(fileSavePath: String = "", providerAuthority: String) {
        synchronized(UpgradeTool::class.java) {
            if (isInit) return

            isInit = true
            this.providerAuthority = providerAuthority
            downloadInfo.apkSavePath = fileSavePath
        }
    }

    /**
     * 下载更新
     * @param activity 依托于 Activity 运行
     * @param targetFileUrl 目标 apk 文件的下载地址
     * @param savedFileName apk 文件的保存名称, 默认是 newVersion.apk
     * @param upgradeListener 下载状态监听器
     * @param orginalMD5 服务器返回的MD5值
     */
    @Synchronized
    fun download(
        activity: FragmentActivity,
        targetFileUrl: String,
        savedFileName: String = "newVersion.apk",
        orginalMD5: String,
        upgradeListener: UpgradeListener.() -> Unit
    ) {
        synchronized(UpgradeTool::class.java) {
            if ((Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) and isInit.not())
                throw IllegalArgumentException("In android N+, you must init UpgradeTool first!")

            UpgradeTool.activity = activity
            innerFun = upgradeListener

            // 下载过了就直接进行安装，不走下载回调
            if (isDownloaded(defaultPath(activity), savedFileName)) {
                if (downloadInfo.apkSavePath.isEmpty()) downloadInfo.apkSavePath =
                    defaultPath(activity)
                downloadInfo.fileName = savedFileName
                onCompleted()
                //install(orginalMD5)
                return
            }


            // set custom config
            downloadInfo.targetFileUrl = targetFileUrl
            downloadInfo.fileName = savedFileName

            // remove exist fragment
            val findFragment = activity.supportFragmentManager.findFragmentByTag(FRAGMENT_TAG)
            if (findFragment != null) activity.supportFragmentManager.commit {
                remove(findFragment)
            }

            // add new fragment
            fragment = DownloadFragment.getInstance(downloadInfo)
            activity.supportFragmentManager.commit {
                add(fragment, FRAGMENT_TAG)
            }
            fragment.bindDownloadListener(this)
        }
    }

    @Synchronized
    @Throws(IllegalArgumentException::class)
    fun install(orginalMD5: String) {
        synchronized(UpgradeTool::class.java) {
            if (
                downloadInfo.apkSavePath.isEmpty()
                or downloadInfo.fileName.isEmpty()
                or providerAuthority.isEmpty()
            )
                throw IllegalArgumentException("Please install apk file first!")


            //安装前MD5校验
            apkVerifyWithMD5(orginalMD5)

            // installAPK()
        }
    }

    /**  安装应用的 Intent type 参数  **/
    private const val INSTALL_INTENT_TYPE = "application/vnd.android.package-archive"

    /**  安装应用的请求码  **/
    const val REQUEST_INSTALL = 9 shl 3

    /**  安装下载好的 apk 文件  **/
    private fun installAPK() {
        val targetFile = File(downloadInfo.apkSavePath, downloadInfo.fileName)
        val intent = Intent(Intent.ACTION_VIEW)
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
            intent.flags = Intent.FLAG_GRANT_READ_URI_PERMISSION
            val fileUri =
                FileProvider.getUriForFile(
                    activity,
                    providerAuthority,
                    targetFile
                )
            intent.setDataAndType(fileUri, INSTALL_INTENT_TYPE)
        } else {
            intent.setDataAndType(Uri.fromFile(targetFile), INSTALL_INTENT_TYPE)
            intent.flags = Intent.FLAG_ACTIVITY_NEW_TASK
        }

        activity.startActivityForResult(intent, REQUEST_INSTALL)
    }


    /**
     * 通过检查apk包的MD5摘要值来判断代码文件是否被篡改
     *
     * @param orginalMD5 原始Apk包的MD5值
     */
    private fun apkVerifyWithMD5(orginalMD5: String) {
        val apkPath = File(downloadInfo.apkSavePath, downloadInfo.fileName)// 获取Apk包存储路径

        try {
            val dexDigest: MessageDigest = MessageDigest.getInstance("MD5")
            val bytes = ByteArray(1024)
            var byteCount: Int = 0;
            val fis = FileInputStream(apkPath) // 读取apk文件
            while (fis.read(bytes).also({ byteCount = it }) != -1) {
                dexDigest.update(bytes, 0, byteCount)
            }
            val bigInteger = BigInteger(1, dexDigest.digest()) // 计算apk文件的哈希值
            val sha: String = bigInteger.toString(16)
            fis.close()
            if (sha == orginalMD5||true) { // 将得到的哈希值与原始的哈希值进行比较校验
                installAPK()

            } else {
                // 验证重新下载
                apkPath.delete()
                listener.apply(innerFun).onFailed?.invoke(Exception("文件MD5值不一致"))
            }
        } catch (e: NoSuchAlgorithmException) {
            e.printStackTrace()
        } catch (e: FileNotFoundException) {
            e.printStackTrace()
        } catch (e: IOException) {
            e.printStackTrace()
        }
    }


    /**  是否已下载完毕  **/
    fun isDownloaded(savePath: String, fileName: String) =
        File(savePath, fileName).exists()

    override fun onProgress(progress: Int) {
        listener.apply(innerFun).onProgress?.invoke(progress)
    }

    override fun onCompleted() {
        listener.apply(innerFun).onCompleted?.invoke()
    }

    override fun onFailed(e: Exception) {
        listener.apply(innerFun).onFailed?.invoke(e)
    }

}


/**  文件保存状态监听器  **/
class UpgradeListener {

    /**  当进度改变时回调  **/
    var onProgress: ((progress: Int) -> Unit)? = null

    /**  当下载完成时回调  **/
    var onCompleted: (() -> Unit)? = null

    /**  当出错时回调  **/
    var onFailed: ((e: Exception) -> Unit)? = null

    /**  当进度改变时回调  **/
    infix fun onProgress(onProgress: (progress: Int) -> Unit) {
        this.onProgress = onProgress
    }

    /**  当下载完成时回调  **/
    infix fun onCompleted(onCompleted: () -> Unit) {
        this.onCompleted = onCompleted
    }

    /**  当出错时回调  **/
    infix fun onFailed(onFailed: (e: Exception) -> Unit) {
        this.onFailed = onFailed
    }

}